[AD] Scalaアプリケーションの開発・保守は合同会社ミルクソフトにお任せください
Scalaを使っているとたまに聞く「implicit conversion」「暗黙の型変換」というキーワード。
なんのことやらよくわからない、という人も多いと思います。
この記事ではそんな暗黙の型変換について解説していきます。
暗黙の型変換とは
使えないはずのメソッドを使えるようにしたり、合わないはずの型を合わせたりする便利機能
- 特定の型
B
が求められているところに別の型A
が使われていて、本来であれば型が合わずにコンパイルエラーになるべきところ、 - スコープ内にあらかじめ
A
をB
に変換できるメソッドf
が定義されていて、- かつ、その使うべき変換メソッドが一意に定まる場合には(つまり、他にも複数あって迷う、というようなことがない場合)、
- その型
A
をメソッドf
を使って自動的に別の型B
に変換する
という機能です。
合わない型をコンパイラがいい感じに(つまり、暗黙的に)別の型へと変換してくれる仕組みです。
暗黙の型変換でメソッドチェーンを実現する
Scala 2.13より導入されたtap
メソッドについて見てみましょう。
tap
メソッドを使う
tapメソッドとは、処理の途中に副作用を挟むためのメソッドです。
1.tap(n => println(s"tapped $n"))
さて、1
はInt
です。
実はInt
はtap
メソッドなどというものは持っていません。
しかしながら、scala.util.chaining
パッケージの中身をインポートすると、1
にtap
メソッドが生えて、使えるようになるのです。
import util.chaining.*
tapped 1
このような不思議な挙動を実現しているのが「暗黙の型変換」です。
tap
メソッドが使えるのは暗黙の型変換が働いたから
scala.util.chaining
というパッケージには、暗黙の型変換を引き起こすためのscalaUtilChainingOps
というメソッドが含まれています。
scala.util.chaining
これがそのメソッドです。
implicit final def scalaUtilChainingOps[A](a: A): ChainingOps[A]
このメソッドはA
という型を受け取って、ChainingOps[A]
という型に変換するメソッドです。
このメソッドを使うと、任意の型A
をChainingOps[A]
に変換することができます。
変換できると、A
があたかもChainingOps[A]
が持っているメソッドを使うことができるかのように振る舞うことができます。
scala.util.ChainingOps
ChainingOps
型について見てみると、このChainingOps
がtap
メソッドを持っていたことがわかります。
前述の例で、単なるInt
であるはずの1
があたかもtap
メソッドを持っているかのように見えたのは、
コンパイラがInt
からChainingOps[Int]
へとこっそり型を変換してくれていたからなのです。
Predef
を知る
Predef
オブジェクトはScalaにおいて勝手に必ず自動的にインポートされるオブジェクトで、ScalaのコードではどこでもPredef
に定義されたコードを使用することができます。
Predef
たくさんのimplicit def
が定義されていますね。
自動的に別の型へと変換するメソッドをあらかじめ定義しておくことで、まるで型にものすごく多くのメソッドが生えているかのように見えるのです。
今までIDEなどの補完処理において見えていたのは、実は変換先の型が持っているメソッドだったのです。
Predef
オブジェクトについてはこちらの記事で詳しく解説しています。
暗黙の型変換は誰にとって嬉しいの?
ライブラリの開発者と使用者向けの機能
暗黙の型変換は、プログラムを書く際に必要以上に型を意識せずに使えるようにできる機能です。
ライブラリは、当該プログラムの書く側よりも使用者や使用回数の方が極端に多いプログラムです。
ライブラリの作者が使用者に対して逐一使い方を教えてあげるわけにはいきません。
使用者が戸惑わないような使い勝手のいいプログラムを書くことが、通常のプログラム開発以上に求められます。
暗黙の型変換は、ライブラリを開発する人がそのライブラリの使い勝手をよくするための機能といっても過言ではないでしょう。
通常の開発では使用を控えましょう
そうでない一般の開発者が良かれと思って使うと混乱のもとになります。
仕組みを見えにくいように隠蔽できる機能であることから、通常の開発現場のような、コミュニケーションコストの比較的低い開発シーンで多用するのはご法度です。
過去数多くの現場で悲劇を生んできた諸刃の剣ですので、使う場合にはよく注意してください。
長いメソッド名の秘密
さて、先程挙げたscalaUtilChainingOps
や、あるいはPredef
に定義されていたimplicit def
には、何やらとっても長い冗長な名前がついていますね。
実は、これは意図的にそのようにつけられています。
直接呼び出すような使い方はしない前提で定義されており、もし直接呼び出したりするととても不格好となって違和感を感じさせるようにできています。
目につくので後々修正してもらいやすいということですね。
自分で暗黙の型変換を定義する際にはぜひ参考にしてください。